package managers;

import static org.junit.Assert.*;

import java.io.IOException;

import messages.BaseMessage;
import messages.GeneralAck;
import messages.MessageType;
import messages.observer.EventCollectionMessage;
import network.managers.ObserverManager;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import state.group.HallCall;
import state.group.events.BaseEvent;
import state.group.events.DeregistrationEvent;
import state.group.events.EventCollection;
import state.group.events.EventType;
import state.group.events.FloorEvent;
import state.group.events.HallCallEvent;
import state.group.events.PersonDispatcherEvent;
import test.BaseTest;

import test.stubs.ObserverStub;

public class ObserverTest extends BaseTest {

    private static ObserverStub obs;
    
    @BeforeClass
    public static void initSystem() throws IOException
    {
        BaseTest.initSystem();
        
        obs = new ObserverStub();
        
        if (!obs.connect(port))
        {
            t.shutdownThread();
            ObserverManager.shutdown();
            HallCallManager.shutdown();
            ElevatorManager.shutdown();
            
            fail("Could not initialize");
        }
        
        //shut down the other threads, so that only the Observer Thread is running.
        HallCallManager.shutdown();
        ElevatorManager.shutdown();
    }
    
    @AfterClass
    public static void teardownSystem() throws IOException
    {
        obs.close();
        BaseTest.teardownSystem();
    }
    
    @Test
    public void testSendToOne() throws IOException
    {
        ObserverManager.getInstance().sendMessageToObservers(new GeneralAck(MessageType.DeregistrationMessage, true));
        
        BaseMessage readMess = obs.readObserver();
        
        assertEquals(MessageType.GeneralAck, readMess.getMessageType());
        
        GeneralAck ack = (GeneralAck) readMess;
        
        assertEquals(MessageType.DeregistrationMessage, ack.getAckMessageType());
        
        assertTrue(ack.getSuccess());
    }
    
    @Test
    public void testSendToAll() throws IOException
    {
        ObserverStub stub1 = new ObserverStub();
        assertTrue(stub1.connect(port));
        
        ObserverStub stub2 = new ObserverStub();
        assertTrue(stub2.connect(port));
        
        ObserverManager.getInstance().sendMessageToObservers(new GeneralAck(MessageType.DeregistrationMessage, true));

        //test obs 1
        BaseMessage readMess = obs.readObserver();
        assertEquals(MessageType.GeneralAck, readMess.getMessageType());
        GeneralAck ack = (GeneralAck) readMess;
        assertEquals(MessageType.DeregistrationMessage, ack.getAckMessageType());
        assertTrue(ack.getSuccess());
        
        //test obs 2
        readMess = stub1.readObserver();
        assertEquals(MessageType.GeneralAck, readMess.getMessageType());
        ack = (GeneralAck) readMess;
        assertEquals(MessageType.DeregistrationMessage, ack.getAckMessageType());
        assertTrue(ack.getSuccess());
        
        //test obs 3
        readMess = stub2.readObserver();
        assertEquals(MessageType.GeneralAck, readMess.getMessageType());
        ack = (GeneralAck) readMess;
        assertEquals(MessageType.DeregistrationMessage, ack.getAckMessageType());
        assertTrue(ack.getSuccess());
        
        stub2.close();
        
        stub1.close();
    }
    
    @Test
    public void testSendSingleEvent() throws IOException
    {
        BaseEvent event = new PersonDispatcherEvent(true);
        
        ObserverManager.getInstance().postEvent(event);
        
        synchronized (ObserverManager.getInstance())
        {
            ObserverManager.getInstance().notify();
        }
        
        BaseMessage result = obs.readObserver();
        
        assertEquals(MessageType.EventCollectionMessage, result.getMessageType());
        
        EventCollectionMessage mess = (EventCollectionMessage) result;
        
        EventCollection coll = mess.getEvents();
        
        assertEquals(1, coll.size());
        
        BaseEvent retEvent = coll.get(0);
        
        assertEquals(EventType.PersonDispatcherEvent, retEvent.getEventType());
        
        PersonDispatcherEvent perEvent = (PersonDispatcherEvent) retEvent;
        
        assertTrue(perEvent.isRegistration());
    }
    
    @Test
    public void testSendMultipleEvents() throws IOException
    {        
        ObserverManager.getInstance().postEvent(new PersonDispatcherEvent(true));
        ObserverManager.getInstance().postEvent(new HallCallEvent(new HallCall((byte)2, (byte)1), false));
        ObserverManager.getInstance().postEvent(new FloorEvent((byte) 1, (byte)4));
        ObserverManager.getInstance().postEvent(new DeregistrationEvent((byte) 1));
        
        synchronized (ObserverManager.getInstance())
        {
            ObserverManager.getInstance().notify();
        }
        
        BaseMessage result = obs.readObserver();
        assertEquals(MessageType.EventCollectionMessage, result.getMessageType());
        
        EventCollectionMessage mess = (EventCollectionMessage) result;
        EventCollection coll = mess.getEvents();
        assertEquals(4, coll.size());
        
        BaseEvent retEvent = coll.get(0);
        assertEquals(EventType.PersonDispatcherEvent, retEvent.getEventType());
        PersonDispatcherEvent perEvent = (PersonDispatcherEvent) retEvent;
        assertTrue(perEvent.isRegistration());
        
        retEvent = coll.get(1);
        assertEquals(EventType.HallCallEvent, retEvent.getEventType());
        HallCallEvent callEvent = (HallCallEvent) retEvent;
        assertFalse(callEvent.isRegistration());
        assertEquals(new HallCall((byte) 2, (byte) 1), callEvent.getHallCall());
        
        retEvent = coll.get(2);
        assertEquals(EventType.FloorEvent, retEvent.getEventType());
        FloorEvent floorEvent = (FloorEvent) retEvent;
        assertEquals((byte) 1, floorEvent.getElevatorId());
        assertEquals((byte) 4, floorEvent.getFloor());
        
        retEvent = coll.get(3);
        assertEquals(EventType.DeregistrationEvent, retEvent.getEventType());
        DeregistrationEvent regEvent = (DeregistrationEvent) retEvent;
        assertEquals((byte) 1, regEvent.getElevatorId());
    }

}
